package com.dd.cloud.gateway.util;


import com.dd.cloud.common.config.common.ProfilesProperties;
import com.dd.cloud.common.config.cookie.CookieProperties;
import com.dd.cloud.common.config.cookie.JwtProperties;
import com.dd.cloud.common.constants.HealthConstants;
import com.dd.cloud.common.constants.JwtConstans;
import com.dd.cloud.common.enums.ZkExceptionEnum;
import com.dd.cloud.common.exception.ZekeException;
import com.dd.cloud.common.utils.CalendarUtil;
import com.dd.cloud.common.utils.ObjectUtils;
import com.dd.cloud.common.vo.LoginInfo;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.joda.time.DateTime;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.HttpHeaders;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;

import java.net.InetAddress;
import java.net.UnknownHostException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.Date;
import java.util.concurrent.TimeUnit;

/**
 * @author zk
 */
@Slf4j
@Configuration
@EnableConfigurationProperties(JwtProperties.class)
public class JwtUtils {
    @Autowired
    private JwtProperties jwtProperties;
    @Autowired
    private StringRedisTemplate redisTemplate;
    @Autowired
    private ProfilesProperties profilesProperties;
    @Autowired
    private CookieProperties cookieProperties;


    /**
     * 私钥加密token
     *
     * @return
     */
    public String generateToken(LoginInfo info) {
        PrivateKey privateKey = jwtProperties.getPrivateKey();
        int expire = jwtProperties.getExpire();
        return Jwts.builder().claim(JwtConstans.JWT_KEY_ID, info.getId())
                .claim(JwtConstans.JWT_KEY_PHAR_ID, info.getPharId())
                .claim(JwtConstans.JWT_MECHAN_ID, info.getMechanId())
                .claim(JwtConstans.JWT_ROLE_ID, info.getRoleId())
                .claim(JwtConstans.JWT_KEY_USER_TYPE, info.getType())
                .claim(JwtConstans.JWT_NAME, info.getName())
                .claim(JwtConstans.JWT_KEY_PLATID, info.getPlatId())
                .setExpiration(DateTime.now().plusMinutes(expire).toDate())
                .claim(JwtConstans.JWT_USER_AGENT, info.getUserAgent())
                .signWith(SignatureAlgorithm.RS256, privateKey).compact();
    }

    /**
     * 公钥解析token
     *
     * @param token     用户请求中的token
     * @param publicKey 公钥
     * @return
     * @throws Exception
     */
    private Jws<Claims> parserToken(String token, PublicKey publicKey) throws Exception {
        return Jwts.parser().setSigningKey(publicKey).parseClaimsJws(token);
    }

    /**
     * 解析token的过期时间
     *
     * @param token
     * @return
     * @throws Exception
     */
    public Date parserTokenExpireDate(String token) throws Exception {
        PublicKey publicKey = jwtProperties.getPublicKey();
        Jws<Claims> jws = parserToken(token, publicKey);
        return jws.getBody().getExpiration();
    }

    /**
     * 校验token是否过期
     *
     * @param token
     * @return true=过期，false反之
     * @throws Exception
     */
    public boolean isTokenExpireDate(String token) throws Exception {
        PublicKey publicKey = jwtProperties.getPublicKey();
        Jws<Claims> jws = parserToken(token, publicKey);
        Date expiration = jws.getBody().getExpiration();
        long dx = CalendarUtil.timeDiff(expiration, new Date());
        return dx <= 0;
    }

    /**
     * 计算token剩余过期时间，单位为分钟
     *
     * @param token
     * @return 剩余过期时间
     * @throws Exception
     */
    public long calcTokenTimeLeft(String token) throws Exception {
        PublicKey publicKey = jwtProperties.getPublicKey();
        Jws<Claims> jws = parserToken(token, publicKey);
        Date expiration = jws.getBody().getExpiration();
        return CalendarUtil.timeDiff(expiration, new Date());
    }

    /**
     * 获取token中的信息
     *
     * @param token 用户请求中的令牌
     * @return 用户信息
     * @throws Exception
     */
    public LoginInfo getInfoFromToken(String token) throws Exception {
        PublicKey publicKey = jwtProperties.getPublicKey();
        Jws<Claims> claimsJws = parserToken(token, publicKey);
        Claims body = claimsJws.getBody();
        Date expireDate = body.getExpiration();
        return new LoginInfo(ObjectUtils.toInt(body.get(JwtConstans.JWT_KEY_ID)),
                ObjectUtils.toInt(body.get(JwtConstans.JWT_KEY_PHAR_ID)),
                ObjectUtils.toInt(body.get(JwtConstans.JWT_MECHAN_ID)),
                ObjectUtils.toInt(body.get(JwtConstans.JWT_ROLE_ID)),
                ObjectUtils.toInt(body.get(JwtConstans.JWT_KEY_USER_TYPE)),
                ObjectUtils.toString(body.get(JwtConstans.JWT_NAME)),
                ObjectUtils.toString(body.get(JwtConstans.JWT_KEY_PLATID)),
                //ObjectUtils.toString(body.get(JwtConstans.JWT_KEY_VERSION)),
                expireDate,
                ObjectUtils.toString(body.get(JwtConstans.JWT_USER_AGENT))
        );
    }

    /**
     * 获取各个端口的cookie名字
     *
     * @param request
     * @return 用户信息
     * @throws Exception
     */
    public String getUserAgent(ServerHttpRequest request) {
        HttpHeaders headers = request.getHeaders();
        String client = headers.getFirst("client");
        if (StringUtils.isBlank(client)) {
            client = "";
        }
        //客户端标识, 同版本会一致
        String userAgent = headers.getFirst("User-Agent");
        if (userAgent == null) {
            userAgent = "";
        }
        //主机名
        String localName = "";
        //根据网卡取本机配置的IP
        InetAddress iNet = null;
        try {
            iNet = InetAddress.getLocalHost();
            localName = iNet.getHostName();
        } catch (UnknownHostException e) {
            log.error("getClientIp error: ", e);
        }
        userAgent = userAgent + localName;
        if (StringUtils.isBlank(client)){
            client = "";
        }
        //不是移动端,加上ip限制, 移动端ip容易更换
        if (!client.equals(HealthConstants.ClientType.HEALTHMOB) &&
                !client.equals(HealthConstants.ClientType.HEALTHPATIENT) &&
                !client.equals(HealthConstants.ClientType.HEALTHAPPLET)) {
            String ip = PathUtil.getIP(request);
            userAgent = userAgent + ip;
        }
        return userAgent;
    }

    /**
     * 获取各个端口的cookie名字
     *
     * @param request
     * @return 用户信息
     * @throws Exception
     */
    public String getCookieName(ServerHttpRequest request) {
        String client = request.getHeaders().getFirst("client");
        return getCookieName(client);
    }

    /**
     * 获取各个端口的cookie名字
     *
     * @return 用户信息
     * @throws Exception
     */
    public String getCookieName(String client) {
        String cookieName = "health_pc_token";
        if (StringUtils.isEmpty(client)) {
            return cookieName;
        }
        log.info("client==" + client);
        switch (client) {
            case HealthConstants.ClientType.HEALTHMOB:
                cookieName = cookieProperties.getHealthMob();
                break;
            case HealthConstants.ClientType.HEALTHMECHAN:
                cookieName = cookieProperties.getHealthMechan();
                break;
            case HealthConstants.ClientType.HEALTHADMIN:
                cookieName = cookieProperties.getHealthAdmin();
                break;
            case HealthConstants.ClientType.HEALTHAGENT:
                cookieName = cookieProperties.getHealthAgent();
                break;
            case HealthConstants.ClientType.HEALTHSALES:
                cookieName = cookieProperties.getHealthSales();
                break;
            case HealthConstants.ClientType.HEALTHPHAR:
                cookieName = cookieProperties.getHealthPhar();
                break;
            case HealthConstants.ClientType.HEALTHAPPLET:
                cookieName = cookieProperties.getHealthApplet();
                break;
            case HealthConstants.ClientType.HEALTHPATIENT:
                cookieName = cookieProperties.getHealthPatient();
                break;
            default:
                cookieName = cookieProperties.getHealthPc();
                break;
        }
        log.info("token名称" + cookieName);
        return cookieName;
    }

    /**
     * 生成token并写入浏览器cookie
     *
     * @param loginInfo
     * @param request
     * @param response
     */
    public void createToken(LoginInfo loginInfo, ServerHttpRequest request, ServerHttpResponse response) {
        try {
            String token = generateToken(loginInfo);
            log.info("登录生成的token={}", token);
            // 无需给前端浏览器返回token，token只有后端才需要使用，并且将token保存到cookie中
            // 将token写入cookie --- 工厂模式
            // httpOnly()：避免别的js代码来操作你的cookie，是一种安全措施
            // charset(): 不需要编码 因为token中没有中文
            // maxAge()： cookie的生命周期，默认是-1，代表
            // 一次会话，浏览器关闭cookie就失效
            // response: 将cookie写入 --- response中有一个方法 addCookie()
            // request: cookie中有域的概念 domain 例如一个cookie只能在www.baidu.com生效，无法在别的域下生效
            // 给cookie绑定一个域，防止别的网站访问你的cookie，也是一种安全措施
            String client = request.getHeaders().getFirst("cookie");
            String sameSite = request.getHeaders().getFirst("sameSite");
            if (profilesProperties.getActive().equals(HealthConstants.ProfilesType.PROD)) {
                sameSite = null;
            }
            CookieUtils.newBuilder(response).httpOnly().request(request).build(getCookieName(request), token, sameSite);
            redisTemplate.opsForValue().set(client + loginInfo.getType() + "-" + loginInfo.getId() + "-" + loginInfo.getType(), token);
            redisTemplate.expire(client + loginInfo.getType() + "-" + loginInfo.getId() + "-" + loginInfo.getType(), jwtProperties.getExpire(), TimeUnit.MINUTES);
        } catch (Exception e) {
            e.printStackTrace();
            throw new ZekeException(ZkExceptionEnum.INVALID_USERNAME_PASSWORD);
        }
    }
}
